package com.intelligent.ispc.core.repository.mongo.impl;

import com.google.common.collect.Lists;
import com.intelligent.ispc.common.mapper.JsonMapper;
import com.intelligent.ispc.common.utils.StringUtils;
import com.intelligent.ispc.core.dto.StationDto;
import com.intelligent.ispc.core.dto.YieldDto;
import com.intelligent.ispc.core.entity.TestItemName;
import com.intelligent.ispc.core.entity.ItemName;
import com.intelligent.ispc.core.entity.TestData;
import com.intelligent.ispc.core.entity.TestStation;
import com.intelligent.ispc.core.repository.mongo.TestDataDao;
import com.intelligent.ispc.utils.Constant;
import com.intelligent.ispc.utils.SystemConfig;
import com.intelligent.ispc.utils.YieldType;
import com.mongodb.AggregationOutput;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.bson.types.ObjectId;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;

import java.util.*;
import java.util.regex.Pattern;

/**
 * Created by julia on 16/3/10.
 */
@Repository
public class TestDataDaoImpl implements TestDataDao {
    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private SystemConfig systemConfig;

    protected JsonMapper mapper = JsonMapper.nonDefaultMapper();


    @Override
    public List<TestData> findTestDatas(Map<String, Object> searchParams) {

        Query query = setQuery(searchParams);
        return mongoTemplate.find(query, TestData.class, Constant.MONGODB_SPC_COLLECTION + searchParams.get("projectId"));
    }

    @Override
    public List<TestData> findTestDatas(Map<String, Object> searchParams, Pageable pageable) {
        Query query = setQuery(searchParams);

        if (pageable != null) {
            query.with(pageable);
        }

        return mongoTemplate.find(query, TestData.class, Constant.MONGODB_SPC_COLLECTION + searchParams.get("projectId"));
    }

    @Override
    public Long findTestDatasCount(Map<String, Object> searchParams) {
        Query query = filterItemsAndDutAttributeOfReturn(searchParams);
        return mongoTemplate.count(query, TestData.class, Constant.MONGODB_SPC_COLLECTION + searchParams.get("projectId"));
    }

    @Override
    @Deprecated
    public List<String> findDistinctTestDatas(Map<String, Object> searchParams, String targeFiled) {
        Query query = filterItemsAndDutAttributeOfReturn(searchParams);
        List<String> results = mongoTemplate.getCollection(Constant.MONGODB_SPC_COLLECTION + searchParams.get("projectId")).distinct(targeFiled, query.getQueryObject());
        return results;
    }

    @Override
    public List<TestStation> findStationOrSlotNo(Map<String, Object> searchParams) {
        Query query = new Query();
        Criteria criteria = new Criteria();
        if (searchParams.containsKey("projectId")) {
            criteria.and("projectId").is(searchParams.get("projectId"));
        }
        if (searchParams.containsKey("stationIds")) {
            criteria.and("stationId").in((List<String>) searchParams.get("stationIds"));
        }
        query.with(new Sort(Sort.Direction.ASC, "stationId"));
        query.addCriteria(criteria);
        return mongoTemplate.find(query, TestStation.class);
    }

    @Override
    public List<String> findDistinctTestItems(Map<String, Object> searchParams) {
        List<String> names = Lists.newArrayList();
        Query query = new Query();
        Criteria criteria = new Criteria();
        if (searchParams.containsKey("projectId")) {
            criteria.and("projectId").is(searchParams.get("projectId"));
        }
        query.addCriteria(criteria);
        TestItemName testItemName = mongoTemplate.findOne(query, TestItemName.class);
        if (testItemName != null && testItemName.getItemNames() != null) {
            for (ItemName itemName : testItemName.getItemNames()) {
                if (itemName.getNumeric() != null && itemName.getNumeric() && itemName.getFileIds() != null && !itemName.getFileIds().isEmpty()) {
                    names.add(itemName.getName());
                }
            }
        }
        Collections.sort(names);
        return names;
    }

    @Override
    public void updateTestData(Map<String, Object> searchParams, Map<String, Object> updateParams) {
        Query query = filterItemsAndDutAttributeOfReturn(searchParams);

        Update update = new Update();
        for (Map.Entry<String, Object> entry : updateParams.entrySet()) {
            String key = entry.getKey();
            if (StringUtils.isNotBlank(key)) {
                update.set(key, entry.getValue());

            }
        }
        mongoTemplate.updateMulti(query, update, TestData.class, Constant.MONGODB_SPC_COLLECTION + searchParams.get("projectId"));
    }

    @Override
    public void findAllAndRemove(Map<String, Object> searchParams) {
        Query query = filterItemsAndDutAttributeOfReturn(searchParams);
        mongoTemplate.findAllAndRemove(query, TestData.class, Constant.MONGODB_SPC_COLLECTION + searchParams.get("projectId"));
    }

    @Override
    public List<YieldDto> findYield(Map<String, Object> searchParams) {
        Map<String, Object> result = new HashMap<>();
        Map<String, Object> groupKeys = new HashMap<>();
        Map<String, Long> totalMap = new HashMap<>();
        Map<String, Map<String, Long>> stationIdMap = new HashMap<>();

        groupKeys.put("testData.stationId", 1);
        groupKeys.put("testData.serialNumber", 1);

        Map<String, Object> resultKey = new HashMap<>();
        resultKey.put("id", 0);
        resultKey.put("firstPass", 0);
        resultKey.put("firstFail", 0);
        resultKey.put("rePass", 0);
        resultKey.put("reFail", 0);
        resultKey.put("total", 0);

        String reduced = "function(doc, aggr){" +
                "if (aggr.total == 0) {" +
                "   aggr.total += 1;" +
                "}"+
                "if (doc.loopCount == 1) {" +
                "   aggr.id = doc._id;" +
                "   if (doc.testData.isPass == true && aggr.firstPass == 0) {" +
                "   aggr.firstPass += 1;" +
                "   aggr.rePass = 0;" +
                "   }"+
                "   if (doc.testData.isPass == false && aggr.firstFail == 0) {" +
                "   aggr.firstFail += 1;" +
                "   }"+
                "}"+
                "else {" +
                "   if (doc.testData.isPass == true && aggr.firstPass == 0 && aggr.rePass == 0) {" +
                "   aggr.rePass += 1;" +
                "   aggr.reFail = 0;" +
                "   }"+
                "   if (doc.testData.isPass == false && aggr.rePass == 0 && aggr.reFail == 0) {" +
                "   aggr.reFail += 1;" +
                "     if (aggr.firstPass != 0) {" +
                "         aggr.reFail = 0;" +
                "     }"+
                "   }"+
                "}"+
                "}";
        Query query = setQuery(searchParams);
        DBObject yieldResult = mongoTemplate.getCollection(Constant.MONGODB_SPC_COLLECTION + searchParams.get("projectId")).group(new BasicDBObject(groupKeys),
                query.getQueryObject(),
                new BasicDBObject(resultKey),
                reduced);

        Map mapResult = yieldResult.toMap();

        totalMap = new HashMap<>();
        for (Object value : mapResult.values()) {
            result = mapper.fromJson(value.toString(), mapper.contructMapType(Map.class, String.class, Object.class));
            Object stationIdObject = result.get("testData.stationId");
            if (stationIdObject == null) {
                continue;
            }
            String stationId = stationIdObject.toString();
            Long firstPass = result.get("firstPass") != null ? Long.valueOf(result.get("firstPass").toString().substring(0,result.get("firstPass").toString().indexOf("."))) : 0;
            Long firstFail = result.get("firstFail") != null ? Long.valueOf(result.get("firstFail").toString().substring(0,result.get("firstFail").toString().indexOf("."))) : 0;
            Long rePass = result.get("rePass") != null ? Long.valueOf(result.get("rePass").toString().substring(0,result.get("rePass").toString().indexOf("."))) : 0;
            Long reFail = result.get("reFail") != null ? Long.valueOf(result.get("reFail").toString().substring(0,result.get("reFail").toString().indexOf("."))) : 0;
            Long total = result.get("total") != null ? Long.valueOf(result.get("total").toString().substring(0,result.get("total").toString().indexOf("."))) : 0;

            String stationIdFirstPass = stationId + "firstPass";
            String stationIdFirstFail = stationId + "firstFail";
            String stationIdRePass = stationId + "rePass";
            String stationIdReFail = stationId + "reFail";
            String totalStr = stationId + "total";


            if (!totalMap.containsKey(stationIdFirstPass)) {
                totalMap.put(stationIdFirstPass, firstPass);
            } else {
                if (firstPass != 0) {
                    Long count = totalMap.get(stationIdFirstPass);
                    totalMap.put(stationIdFirstPass,  count + firstPass);
                }
            }

            if (!totalMap.containsKey(stationIdFirstFail)) {
                totalMap.put(stationIdFirstFail, firstFail);
            } else {
                if (firstFail != 0) {
                    Long count = totalMap.get(stationIdFirstFail);
                    totalMap.put(stationIdFirstFail,  count + firstFail);
                }
            }

            if (!totalMap.containsKey(stationIdRePass)) {
                totalMap.put(stationIdRePass, rePass);
            } else {
                if (rePass != 0) {
                    Long count = totalMap.get(stationIdRePass);
                    totalMap.put(stationIdRePass,  count + rePass);
                }
            }

            if (!totalMap.containsKey(stationIdReFail)) {
                totalMap.put(stationIdReFail, reFail);
            } else {
                if (reFail != 0) {
                    Long count = totalMap.get(stationIdReFail);
                    totalMap.put(stationIdReFail,  count + reFail);
                }
            }

            if (!totalMap.containsKey(totalStr)) {
                totalMap.put(totalStr, total);
            } else {
                if (total != 0) {
                    Long count = totalMap.get(totalStr);
                    totalMap.put(totalStr,  count + total);
                }
            }

            if (stationIdMap.containsKey(stationId)) {
                stationIdMap.get(stationId).put(stationIdFirstPass, totalMap.get(stationIdFirstPass));
                stationIdMap.get(stationId).put(stationIdFirstFail, totalMap.get(stationIdFirstFail));
                stationIdMap.get(stationId).put(stationIdRePass, totalMap.get(stationIdRePass));
                stationIdMap.get(stationId).put(stationIdReFail, totalMap.get(stationIdReFail));
                stationIdMap.get(stationId).put(totalStr, totalMap.get(totalStr));
            } else {
                stationIdMap.put(stationId, totalMap);
            }
        }
        List<YieldDto> yieldDtos = Lists.newArrayList();

        for (Map.Entry<String, Map<String, Long>> entry: stationIdMap.entrySet()) {
            String key = entry.getKey();
            Map<String, Long> value = entry.getValue();
            YieldDto yieldDto = new YieldDto();
            yieldDto.setStationName(key);
            if (value.containsKey(key + "firstPass")) {
                long firstPass = value.get(key + "firstPass");
                yieldDto.setFirstPass(firstPass);
            }
            if (value.containsKey(key + "firstFail")) {
                long firstFail = value.get(key + "firstFail");
                yieldDto.setFirstFail(firstFail);
            }
            if (value.containsKey(key + "rePass")) {
                long rePass = value.get(key + "rePass");
                yieldDto.setReTestPass(rePass);
            }
            if (value.containsKey(key + "reFail")) {
                long reFail = value.get(key + "reFail");
                yieldDto.setReTestFail(reFail);
            }

            if (value.containsKey(key + "total")) {
                long total = value.get(key + "total");
                yieldDto.setTotolInspect(total);
            }
            yieldDto.setFirstInspect(yieldDto.getFirstPass() + yieldDto.getFirstFail());
            yieldDto.setReTestInspect(yieldDto.getReTestPass() + yieldDto.getReTestFail());
            yieldDto.setYieldCount();
            yieldDtos.add(yieldDto);
        }
        return yieldDtos;
    }

    @Override
    public List<ObjectId> findFistTestDataId(Map<String, Object> searchParams) {
        List<ObjectId> firstIds = Lists.newArrayList();
        // create our pipeline operations, first with the $match
        Map<String, Object> paramMaps = new HashMap<>();
        paramMaps.put("projectId", searchParams.get("projectId"));
        paramMaps.put("isDelete", false);

        DBObject match = new BasicDBObject("$match", new BasicDBObject(paramMaps));
        // Finally the $sort operation
        DBObject sort = new BasicDBObject("$sort", new BasicDBObject("testData.stopTime", 1));
        // Now the $group operation
        Map<String, Object> groupKeys = new HashMap<>();
        groupKeys.put("stationId", "$testData.stationId");
        groupKeys.put("serialNumber", "$testData.serialNumber");

        DBObject groupFields = new BasicDBObject();
        groupFields.put("_id",groupKeys);
        groupFields.put("firstId",new BasicDBObject("$first","$_id"));
        DBObject group = new BasicDBObject("$group", groupFields);

        // build the $projection operation
        DBObject fields = new BasicDBObject("firstId", 1);
        DBObject project = new BasicDBObject("$project", fields);
        // run aggregation
        List<DBObject> pipeline = Arrays.asList(match, sort, group, project);
        AggregationOutput output = mongoTemplate.getCollection(Constant.MONGODB_SPC_COLLECTION + searchParams.get("projectId")).aggregate(pipeline);

        for (DBObject result : output.results()) {
            firstIds.add((ObjectId) result.get("firstId"));
        }
        return firstIds;
    }

    private Query setQuery(Map<String, Object> searchParams) {

        DateTimeFormatter format = DateTimeFormat.forPattern(systemConfig.getDateFormat());
        Query query = new Query();
        Criteria criteria = new Criteria();
        if (searchParams.containsKey("ids")) {
            criteria.and("_id").in((List<ObjectId>) searchParams.get("ids"));
        }

        if (searchParams.containsKey("id")) {
            ObjectId id = new ObjectId(searchParams.get("id").toString());
            criteria.and("_id").is(id);
        }


        /*if (searchParams.containsKey("projectId")) {
            criteria.and("projectId").is(searchParams.get("projectId"));
        }*/

        /*if (searchParams.containsKey("itemName")) {
            criteria.and("testItems.name").is(searchParams.get("itemName"));
        }*/

       /* if (searchParams.containsKey("stationId")) {
            criteria.and("testData.stationId").is(searchParams.get("stationId"));
        }

        if (searchParams.containsKey("slotNo")) {
            criteria.and("testData.slotNo").is(searchParams.get("slotNo"));
        }*/

        if (searchParams.containsKey("serialNumber")) {
            Pattern pattern = Pattern.compile("^.*?" + searchParams.get("serialNumber") + ".*?$", Pattern.CASE_INSENSITIVE);
            criteria.and("testData.serialNumber").regex(pattern);
        }


        if (searchParams.containsKey("testFileUUID")) {
            criteria.and("testFileUUID").is(searchParams.get("testFileUUID"));
        }

        if (searchParams.containsKey("type") && searchParams.get("type") != null) {

            int c = Integer.parseInt((String) searchParams.get("type"));

            if(c == YieldType.TYPE_0.getValue()) {
                criteria.and("loopCount").is(1);
            }

            if (c == YieldType.TYPE_1.getValue()) {
                criteria.and("testData.isPass").is(true);
                criteria.and("loopCount").is(1);
            }
            if (c == YieldType.TYPE_2.getValue()) {
                criteria.and("testData.isPass").is(false);
                criteria.and("loopCount").is(1);
            }

            if (c == YieldType.TYPE_3.getValue()){
                criteria.and("loopCount").ne(1);
            }

            if (c == YieldType.TYPE_4.getValue()) {
                criteria.and("testData.isPass").is(true);
                criteria.and("loopCount").ne(1);
            }
            if (c == YieldType.TYPE_5.getValue()) {
                criteria.and("testData.isPass").is(false);
                criteria.and("loopCount").ne(1);
            }
        }
        if (searchParams.containsKey("loopCount")) {
            if ("is".equals(searchParams.get("loopCount"))) {
                criteria.and("loopCount").is(1);
            } else if ("gt".equals(searchParams.get("loopCount"))) {
                criteria.and("loopCount").ne(1);
            }
        }

        if (searchParams.containsKey("isPass")) {
            criteria.and("testData.isPass").is(searchParams.get("isPass"));
        }

        if (searchParams.containsKey("isDelete")) {
            criteria.and("isDelete").is(searchParams.get("isDelete"));
        } else {
            criteria.and("isDelete").is(false);
        }

        if (searchParams.containsKey("startTime") && !searchParams.containsKey("stopTime")) {
            criteria.and("testData.startTime").gte(DateTime.parse(searchParams.get("startTime").toString(), format).toDate());
        } else if (searchParams.containsKey("startTime") && searchParams.containsKey("stopTime")) {
            criteria.and("testData.startTime").gte(DateTime.parse(searchParams.get("startTime").toString(), format).toDate()).lte(DateTime.parse(searchParams.get("stopTime").toString(), format).toDate());

        } else if (!searchParams.containsKey("startTime") && searchParams.containsKey("stopTime")) {
            criteria.and("testData.startTime").lte(DateTime.parse(searchParams.get("stopTime").toString(), format).toDate());
        }

        query.with(new Sort(Sort.Direction.ASC, "testData.stopTime"));

        if (searchParams.containsKey("itemName")) {
            query.fields().include("testData");
            query.fields().include("projectId");
            query.fields().include("testFileUUID");
            query.fields().include("_id");
            Criteria resultCriteria = Criteria.where("name").is(searchParams.get("itemName"));
            query.fields().elemMatch("testItems", resultCriteria);
        }

        if (searchParams.containsKey("DESC")) {
            query.with(new Sort(Sort.Direction.DESC, "testData.stopTime"));
        }

        if (searchParams.containsKey("isNeedItems") && !(boolean) searchParams.get("isNeedItems")) {
            query.fields().exclude("testItems");
        }

        if (searchParams.containsKey("isNeedDutAttributes") && !(boolean) searchParams.get("isNeedDutAttributes")) {
            query.fields().exclude("dutAttributes");
        }

        Criteria[] criterias = null;
        if (searchParams.containsKey("stationIdOrSlotNo")) {
            List<StationDto> stationDtoList = mapper.fromJson((String) searchParams.get("stationIdOrSlotNo"), mapper.contructCollectionType(List.class, StationDto.class));
            if (stationDtoList != null && !stationDtoList.isEmpty()) {
                criterias = new Criteria[stationDtoList.size()];
                for (int i = 0; i < stationDtoList.size(); i++) {
                    Criteria criteria1 = new Criteria();
                    if (StringUtils.isNotBlank(stationDtoList.get(i).getStationId()) || stationDtoList.get(i).getStationId() == "") {
                        criteria1.and("testData.stationId").is(stationDtoList.get(i).getStationId());
                    }
                    if (stationDtoList.get(i).getSlotNos() != null && !stationDtoList.get(i).getSlotNos().isEmpty()) {
                        criteria1.and("testData.slotNo").in(stationDtoList.get(i).getSlotNos());
                    }
                    criterias[i] = criteria1;
                }
            }
         }
        if (criterias == null) {
            query.addCriteria(criteria);
        } else
            query.addCriteria(criteria.orOperator(criterias));

        return  query;
    }

    private Query filterItemsAndDutAttributeOfReturn(Map<String, Object> searchParams) {

        DateTimeFormatter format = DateTimeFormat.forPattern(systemConfig.getDateFormat());
        Query query = new Query();
        Criteria criteria = new Criteria();
        if (searchParams.containsKey("ids")) {
            criteria.and("_id").in((List<ObjectId>) searchParams.get("ids"));
        }

        if (searchParams.containsKey("id")) {
            ObjectId id = new ObjectId(searchParams.get("id").toString());
            criteria.and("_id").is(id);
        }

        if (searchParams.containsKey("serialNumber")) {
            Pattern pattern = Pattern.compile("^.*?" + searchParams.get("serialNumber") + ".*?$", Pattern.CASE_INSENSITIVE);
            criteria.and("testData.serialNumber").regex(pattern);
        }


        if (searchParams.containsKey("testFileUUID")) {
            criteria.and("testFileUUID").is(searchParams.get("testFileUUID"));
        }

        if (searchParams.containsKey("type") && searchParams.get("type") != null) {

            int c = Integer.parseInt((String) searchParams.get("type"));

            if(c == YieldType.TYPE_0.getValue()) {
                criteria.and("loopCount").is(1);
            }

            if (c == YieldType.TYPE_1.getValue()) {
                criteria.and("testData.isPass").is(true);
                criteria.and("loopCount").is(1);
            }
            if (c == YieldType.TYPE_2.getValue()) {
                criteria.and("testData.isPass").is(false);
                criteria.and("loopCount").is(1);
            }

            if (c == YieldType.TYPE_3.getValue()){
                criteria.and("loopCount").ne(1);
            }

            if (c == YieldType.TYPE_4.getValue()) {
                criteria.and("testData.isPass").is(true);
                criteria.and("loopCount").ne(1);
            }
            if (c == YieldType.TYPE_5.getValue()) {
                criteria.and("testData.isPass").is(false);
                criteria.and("loopCount").ne(1);
            }
        }
        if (searchParams.containsKey("loopCount")) {
            if ("is".equals(searchParams.get("loopCount"))) {
                criteria.and("loopCount").is(1);
            } else if ("gt".equals(searchParams.get("loopCount"))) {
                criteria.and("loopCount").ne(1);
            }
        }

        if (searchParams.containsKey("isPass")) {
            criteria.and("testData.isPass").is(searchParams.get("isPass"));
        }

        if (searchParams.containsKey("isDelete")) {
            criteria.and("isDelete").is(searchParams.get("isDelete"));
        } else {
            criteria.and("isDelete").is(false);
        }

        if (searchParams.containsKey("startTime") && !searchParams.containsKey("stopTime")) {
            criteria.and("testData.startTime").gte(DateTime.parse(searchParams.get("startTime").toString(), format).toDate());
        } else if (searchParams.containsKey("startTime") && searchParams.containsKey("stopTime")) {
            criteria.and("testData.startTime").gte(DateTime.parse(searchParams.get("startTime").toString(), format).toDate()).lte(DateTime.parse(searchParams.get("stopTime").toString(), format).toDate());

        } else if (!searchParams.containsKey("startTime") && searchParams.containsKey("stopTime")) {
            criteria.and("testData.startTime").lte(DateTime.parse(searchParams.get("stopTime").toString(), format).toDate());
        }

        query.with(new Sort(Sort.Direction.ASC, "testData.stopTime"));


        if (searchParams.containsKey("DESC")) {
            query.with(new Sort(Sort.Direction.DESC, "testData.stopTime"));
        }

        query.fields().exclude("testItems");
        query.fields().exclude("dutAttributes");

        Criteria[] criterias = null;
        if (searchParams.containsKey("stationIdOrSlotNo")) {
            List<StationDto> stationDtoList = mapper.fromJson((String) searchParams.get("stationIdOrSlotNo"), mapper.contructCollectionType(List.class, StationDto.class));
            if (stationDtoList != null && !stationDtoList.isEmpty()) {
                criterias = new Criteria[stationDtoList.size()];
                for (int i = 0; i < stationDtoList.size(); i++) {
                    Criteria criteria1 = new Criteria();
                    if (StringUtils.isNotBlank(stationDtoList.get(i).getStationId()) || stationDtoList.get(i).getStationId() == "") {
                        criteria1.and("testData.stationId").is(stationDtoList.get(i).getStationId());
                    }
                    if (stationDtoList.get(i).getSlotNos() != null && !stationDtoList.get(i).getSlotNos().isEmpty()) {
                        criteria1.and("testData.slotNo").in(stationDtoList.get(i).getSlotNos());
                    }
                    criterias[i] = criteria1;
                }
            }
        }
        if (criterias == null) {
            query.addCriteria(criteria);
        } else
            query.addCriteria(criteria.orOperator(criterias));

        return  query;
    }
}
